Share glyph and icon caches
authorMatthias Clasen <mclasen@redhat.com>
Tue, 4 Jun 2019 21:27:09 +0000 (21:27 +0000)
committerMatthias Clasen <mclasen@redhat.com>
Tue, 4 Jun 2019 23:00:02 +0000 (23:00 +0000)
Use the same texture atlases to back both
the glyph and icon caches, and unify their
sizes and management. Store big glyphs
in separate textures, so all atlases have
the same size. Tweak some of the eviction
parameters.

We share the caches across all GL contexts
on a display, unless the GSK_NO_SHARED_CACHES
env var is set.

gsk/gl/gskglglyphcache.c
gsk/gl/gskglglyphcacheprivate.h
gsk/gl/gskgliconcache.c
gsk/gl/gskgliconcacheprivate.h
gsk/gl/gskglrenderer.c
gsk/gl/gskgltextureatlas.c
gsk/gl/gskgltextureatlasprivate.h

index b1338d9e58405d404a3cdec0565c97f9b87cb8e8..6d8872a6cd8c400b14bd70e19cb205f9ad3d6fb1 100644 (file)
 #include <cairo.h>
 #include <epoxy/gl.h>
 
-/* Parameters for our cache eviction strategy.
+/* Cache eviction strategy
  *
- * Each cached glyph has an age that gets reset every time a cached glyph gets used.
- * Glyphs that have not been used for the MAX_AGE frames are considered old. We keep
- * count of the pixels of each atlas that are taken up by old glyphs. We check the
- * fraction of old pixels every CHECK_INTERVAL frames, and if it is above MAX_OLD_RATIO, then
- * we drop the atlas an all the glyphs contained in it from the cache.
+ * Each cached glyph has an age that gets reset every time a cached
+ * glyph gets used. Glyphs that have not been used for the
+ * MAX_FRAME_AGE frames are considered old.
+ *
+ * We keep count of the pixels of each atlas that are taken up by old
+ * data. When the fraction of old pixels gets too high, we drop the
+ * atlas and all the items it contained.
  */
 
-#define MAX_AGE 60
-#define CHECK_INTERVAL 10
-#define MAX_OLD_RATIO 0.333
-
-#define ATLAS_SIZE 512
+#define MAX_FRAME_AGE (5 * 60)
 
 static guint    glyph_cache_hash       (gconstpointer v);
 static gboolean glyph_cache_equal      (gconstpointer v1,
@@ -33,59 +31,47 @@ static gboolean glyph_cache_equal      (gconstpointer v1,
 static void     glyph_cache_key_free   (gpointer      v);
 static void     glyph_cache_value_free (gpointer      v);
 
-static GskGLTextureAtlas *
-create_atlas (GskGLGlyphCache *self,
-              int              width,
-              int              height)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_new (GdkDisplay *display,
+                        GskGLTextureAtlases *atlases)
 {
-  GskGLTextureAtlas *atlas;
+  GskGLGlyphCache *glyph_cache;
 
-  atlas = g_new (GskGLTextureAtlas, 1);
-  gsk_gl_texture_atlas_init (atlas, MAX (width, ATLAS_SIZE), MAX (height, ATLAS_SIZE));
+  glyph_cache = g_new0 (GskGLGlyphCache, 1);
 
-  GSK_NOTE(GLYPH_CACHE, g_message ("Create atlas %d x %d", atlas->width, atlas->height));
-
-  return atlas;
-}
+  glyph_cache->display = display;
+  glyph_cache->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
+                                                   glyph_cache_key_free, glyph_cache_value_free);
 
-static void
-free_atlas (gpointer v)
-{
-  GskGLTextureAtlas *atlas = v;
+  glyph_cache->atlases = gsk_gl_texture_atlases_ref (atlases);
 
-  g_assert (atlas->image.texture_id == 0);
-  gsk_gl_texture_atlas_free (atlas);
+  glyph_cache->ref_count = 1;
 
-  g_free (atlas);
+  return glyph_cache;
 }
 
-void
-gsk_gl_glyph_cache_init (GskGLGlyphCache *self)
+GskGLGlyphCache *
+gsk_gl_glyph_cache_ref (GskGLGlyphCache *self)
 {
-  self->hash_table = g_hash_table_new_full (glyph_cache_hash, glyph_cache_equal,
-                                            glyph_cache_key_free, glyph_cache_value_free);
-  self->atlases = g_ptr_array_new_with_free_func (free_atlas);
+  self->ref_count++;
+
+  return self;
 }
 
 void
-gsk_gl_glyph_cache_free (GskGLGlyphCache *self,
-                         GskGLDriver     *driver)
+gsk_gl_glyph_cache_unref (GskGLGlyphCache *self)
 {
-  guint i;
+  g_assert (self->ref_count > 0);
 
-  for (i = 0; i < self->atlases->len; i ++)
+  if (self->ref_count == 1)
     {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image.texture_id != 0)
-        {
-          gsk_gl_image_destroy (&atlas->image, driver);
-          atlas->image.texture_id = 0;
-        }
+      gsk_gl_texture_atlases_unref (self->atlases);
+      g_hash_table_unref (self->hash_table);
+      g_free (self);
+      return;
     }
 
-  g_ptr_array_unref (self->atlases);
-  g_hash_table_unref (self->hash_table);
+  self->ref_count--;
 }
 
 static gboolean
@@ -122,83 +108,11 @@ glyph_cache_value_free (gpointer v)
   g_free (v);
 }
 
-static void
-add_to_cache (GskGLGlyphCache  *cache,
-              GlyphCacheKey    *key,
-              GskGLCachedGlyph *value)
-{
-  const int width = value->draw_width * key->scale / 1024;
-  const int height = value->draw_height * key->scale / 1024;
-  GskGLTextureAtlas *atlas = NULL;
-  guint i, p;
-  int packed_x, packed_y;
-
-  /* Try all the atlases and pick the first one that can hold
-   * our new glyph */
-  for (i = 0, p = cache->atlases->len; i < p; i ++)
-    {
-      GskGLTextureAtlas *test_atlas = g_ptr_array_index (cache->atlases, i);
-      gboolean was_packed;
-
-      was_packed = gsk_gl_texture_atlas_pack (test_atlas, width, height, &packed_x, &packed_y);
-
-      if (was_packed)
-        {
-          atlas = test_atlas;
-          break;
-        }
-    }
-
-  if (atlas == NULL)
-    {
-      gboolean was_packed;
-
-      atlas = create_atlas (cache, width + 2, height + 2);
-
-      g_ptr_array_add (cache->atlases, atlas);
-
-      was_packed = gsk_gl_texture_atlas_pack (atlas,
-                                              width + 2, height + 2,
-                                              &packed_x, &packed_y);
-
-      g_assert (was_packed);
-    }
-
-  value->tx = (float)(packed_x + 1) / atlas->width;
-  value->ty = (float)(packed_y + 1) / atlas->height;
-  value->tw = (float)width    / atlas->width;
-  value->th = (float)height   / atlas->height;
-  value->used = TRUE;
-
-  value->atlas = atlas;
-
-  if (atlas->user_data == NULL)
-    atlas->user_data = g_new0 (DirtyGlyph, 1);
-
-  ((DirtyGlyph *)atlas->user_data)->key = key;
-  ((DirtyGlyph *)atlas->user_data)->value = value;
-
-#ifdef G_ENABLE_DEBUG
-  if (GSK_DEBUG_CHECK (GLYPH_CACHE))
-    {
-      for (i = 0; i < cache->atlases->len; i++)
-        {
-          atlas = g_ptr_array_index (cache->atlases, i);
-          g_message ("atlas %d (%dx%d): %.2g%% old pixels",
-                   i, atlas->width, atlas->height,
-                   gsk_gl_texture_atlas_get_unused_ratio (atlas));
-        }
-    }
-#endif
-}
-
 static gboolean
-render_glyph (const GskGLTextureAtlas *atlas,
-              const DirtyGlyph        *glyph,
-              GskImageRegion          *region)
+render_glyph (GlyphCacheKey    *key,
+              GskGLCachedGlyph *value,
+              GskImageRegion   *region)
 {
-  GlyphCacheKey *key = glyph->key;
-  GskGLCachedGlyph *value = glyph->value;
   cairo_surface_t *surface;
   cairo_t *cr;
   cairo_scaled_font_t *scaled_font;
@@ -210,16 +124,14 @@ render_glyph (const GskGLTextureAtlas *atlas,
 
   scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
   if (G_UNLIKELY (!scaled_font || cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS))
-    return FALSE;
+    {
+      g_warning ("Failed to get a font");
+      return FALSE;
+    }
 
   surface_width = value->draw_width * key->scale / 1024;
   surface_height = value->draw_height * key->scale / 1024;
 
-  /* TODO: Give glyphs that large their own texture in the proper size. Don't
-   *       put them in the atlas at all. */
-  if (surface_width > atlas->width || surface_height > atlas->height)
-    return FALSE;
-
   stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, surface_width);
   data = g_malloc0 (stride * surface_height);
   surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
@@ -252,45 +164,120 @@ render_glyph (const GskGLTextureAtlas *atlas,
   region->height = cairo_image_surface_get_height (surface);
   region->stride = cairo_image_surface_get_stride (surface);
   region->data = data;
-  region->x = (gsize)(value->tx * atlas->width);
-  region->y = (gsize)(value->ty * atlas->height);
+  if (value->atlas)
+    {
+      region->x = (gsize)(value->tx * value->atlas->width);
+      region->y = (gsize)(value->ty * value->atlas->height);
+    }
+  else
+    {
+      region->x = 0;
+      region->y = 0;
+    }
 
   cairo_surface_destroy (surface);
+
   return TRUE;
 }
 
 static void
-upload_dirty_glyph (GskGLGlyphCache   *self,
-                    GskGLTextureAtlas *atlas,
-                    GskGLDriver       *driver)
+upload_glyph (GlyphCacheKey    *key,
+              GskGLCachedGlyph *value)
 {
-  GskImageRegion region;
-
-  g_assert (atlas->user_data != NULL);
+  GskImageRegion r;
 
-  gdk_gl_context_push_debug_group_printf (gsk_gl_driver_get_gl_context (driver),
-                                          "Uploading glyph %d", ((DirtyGlyph *)atlas->user_data)->key->glyph);
+  gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                          "Uploading glyph %d",
+                                          key->glyph);
 
-  if (render_glyph (atlas, (DirtyGlyph *)atlas->user_data, &region))
+  if (render_glyph (key, value, &r))
     {
+      glBindTexture (GL_TEXTURE_2D, value->texture_id);
+      glTextureSubImage2D (value->texture_id, 0,
+                           r.x, r.y, r.width, r.height,
+                           GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+                           r.data);
+      g_free (r.data);
+    }
 
-      gsk_gl_image_upload_region (&atlas->image, driver, &region);
+  gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+}
 
-      g_free (region.data);
-    }
+static void
+add_to_cache (GskGLGlyphCache  *self,
+              GlyphCacheKey    *key,
+              GskGLCachedGlyph *value)
+{
+  const int width = value->draw_width * key->scale / 1024;
+  const int height = value->draw_height * key->scale / 1024;
+  GskGLTextureAtlas *atlas = NULL;
+  int packed_x = 0;
+  int packed_y = 0;
+
+  gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
+
+  value->tx = (float)(packed_x + 1) / atlas->width;
+  value->ty = (float)(packed_y + 1) / atlas->height;
+  value->tw = (float)width / atlas->width;
+  value->th = (float)height / atlas->height;
+  value->used = TRUE;
+
+  value->atlas = atlas;
+  value->texture_id = atlas->texture_id;
+
+  upload_glyph (key, value);
+}
+
+void
+gsk_gl_glyph_cache_get_texture (GskGLDriver      *driver,
+                                PangoFont        *font,
+                                PangoGlyph        glyph,
+                                float             scale,
+                                GskGLCachedGlyph *value)
+{
+  PangoRectangle ink_rect;
+  GlyphCacheKey key;
+  int width, height;
+  guint texture_id;
+
+  pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
+  pango_extents_to_pixels (&ink_rect, NULL);
+
+  key.font = font;
+  key.glyph = glyph;
+  key.scale = (guint)(scale * 1024);
+
+  value->atlas = NULL;
+  value->timestamp = 0;
+
+  value->draw_x = ink_rect.x;
+  value->draw_y = ink_rect.y;
+  value->draw_width = ink_rect.width;
+  value->draw_height = ink_rect.height;
+
+  value->tx = 0.0f;
+  value->ty = 0.0f;
+  value->tw = 1.0f;
+  value->th = 1.0f;
+
+  width = value->draw_width * key.scale / 1024;
+  height = value->draw_height * key.scale / 1024;
+
+  texture_id = gsk_gl_driver_create_texture (driver, width, height);
+  gsk_gl_driver_bind_source_texture (driver, texture_id);
+  gsk_gl_driver_init_texture_empty (driver, texture_id, GL_NEAREST, GL_NEAREST);
 
-  gdk_gl_context_pop_debug_group (gsk_gl_driver_get_gl_context (driver));
-  /* TODO: This could be unnecessary. We can just reuse the allocated
-   *       DirtyGlyph next time. */
-  g_clear_pointer (&atlas->user_data, g_free);
+  value->texture_id = texture_id;
+
+  upload_glyph (&key, value);
 }
 
-const GskGLCachedGlyph *
-gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
-                           gboolean         create,
-                           PangoFont       *font,
-                           PangoGlyph       glyph,
-                           float            scale)
+gboolean
+gsk_gl_glyph_cache_lookup (GskGLGlyphCache  *cache,
+                           PangoFont        *font,
+                           PangoGlyph        glyph,
+                           float             scale,
+                           GskGLCachedGlyph *cached_glyph_out)
 {
   GskGLCachedGlyph *value;
 
@@ -305,7 +292,7 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
     {
       const guint age = cache->timestamp - value->timestamp;
 
-      if (MAX_AGE <= age)
+      if (age > MAX_FRAME_AGE)
         {
           GskGLTextureAtlas *atlas = value->atlas;
 
@@ -321,116 +308,68 @@ gsk_gl_glyph_cache_lookup (GskGLGlyphCache *cache,
       value->timestamp = cache->timestamp;
     }
 
-  if (create && value == NULL)
+  if (value == NULL)
     {
-      GlyphCacheKey *key;
       PangoRectangle ink_rect;
 
-      key = g_new0 (GlyphCacheKey, 1);
-      value = g_new0 (GskGLCachedGlyph, 1);
-
       pango_font_get_glyph_extents (font, glyph, &ink_rect, NULL);
       pango_extents_to_pixels (&ink_rect, NULL);
 
+      value = g_new0 (GskGLCachedGlyph, 1);
+
       value->draw_x = ink_rect.x;
       value->draw_y = ink_rect.y;
       value->draw_width = ink_rect.width;
       value->draw_height = ink_rect.height;
       value->timestamp = cache->timestamp;
       value->atlas = NULL; /* For now */
-      value->scale = (guint)(scale * 1024);
-
-      key->font = g_object_ref (font);
-      key->glyph = glyph;
-      key->scale = (guint)(scale * 1024);
 
-      if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
-        add_to_cache (cache, key, value);
-
-      g_hash_table_insert (cache->hash_table, key, value);
-    }
+      if (ink_rect.width < 128 && ink_rect.height < 128)
+        {
+          GlyphCacheKey *key;
 
-  return value;
-}
+          key = g_new0 (GlyphCacheKey, 1);
 
-guint
-gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache        *self,
-                                         GskGLDriver            *driver,
-                                         const GskGLCachedGlyph *glyph)
-{
-  GskGLTextureAtlas *atlas = glyph->atlas;
+          key->font = g_object_ref (font);
+          key->glyph = glyph;
+          key->scale = (guint)(scale * 1024);
 
-  g_assert (atlas != NULL);
+          if (ink_rect.width > 0 && ink_rect.height > 0 && key->scale > 0)
+            add_to_cache (cache, key, value);
 
-  if (atlas->image.texture_id == 0)
+          *cached_glyph_out = *value;
+          g_hash_table_insert (cache->hash_table, key, value);
+        }
+      else
+        {
+          *cached_glyph_out = *value;
+          glyph_cache_value_free (value);
+        }
+    }
+  else
     {
-      gsk_gl_image_create (&atlas->image, driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
-      gdk_gl_context_label_object_printf (gsk_gl_driver_get_gl_context (driver),
-                                          GL_TEXTURE, atlas->image.texture_id,
-                                          "Glyph atlas %d", atlas->image.texture_id);
+      *cached_glyph_out = *value;
     }
 
-  if (atlas->user_data != NULL)
-    upload_dirty_glyph (self, atlas, driver);
-
-  return atlas->image.texture_id;
+  return cached_glyph_out->atlas != NULL;
 }
 
 void
-gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
-                                GskGLDriver     *driver)
+gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self)
 {
-  int i;
   GHashTableIter iter;
   GlyphCacheKey *key;
   GskGLCachedGlyph *value;
-  GHashTable *removed = g_hash_table_new (g_direct_hash, g_direct_equal);
   guint dropped = 0;
 
   self->timestamp++;
 
-  if ((self->timestamp - 1) % CHECK_INTERVAL != 0)
-    return;
-
-  /* look for atlases to drop, and create a mapping of updated texture indices */
-  for (i = self->atlases->len - 1; i >= 0; i--)
-    {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
-        {
-          GSK_NOTE(GLYPH_CACHE,
-                   g_message ("Dropping atlas %d (%g.2%% old)", i,
-                              gsk_gl_texture_atlas_get_unused_ratio (atlas)));
-
-#if 0
-          static int kk;
-
-          g_message ("Dropping glyph cache... Ratio: %f",
-                     gsk_gl_texture_atlas_get_unused_ratio (atlas));
-          gsk_gl_image_write_to_png (&atlas->image, driver,
-                                     g_strdup_printf ("dropped_%d.png", kk++));
-#endif
-
-          if (atlas->image.texture_id != 0)
-            {
-              gsk_gl_image_destroy (&atlas->image, driver);
-              atlas->image.texture_id = 0;
-            }
-
-          g_hash_table_add (removed, atlas);
-
-          g_ptr_array_remove_index (self->atlases, i);
-       }
-    }
-
-  /* Remove all glyphs whose atlas was removed, and
-   * mark old glyphs as unused
-   */
   g_hash_table_iter_init (&iter, self->hash_table);
   while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
     {
-      if (g_hash_table_contains (removed, value->atlas))
+      guint pos;
+
+      if (!g_ptr_array_find (self->atlases->atlases, value->atlas, &pos))
         {
           g_hash_table_iter_remove (&iter);
           dropped++;
@@ -439,7 +378,7 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
         {
           const guint age = self->timestamp - value->timestamp;
 
-          if (MAX_AGE <= age && age < MAX_AGE + CHECK_INTERVAL)
+          if (age > MAX_FRAME_AGE)
             {
               GskGLTextureAtlas *atlas = value->atlas;
 
@@ -451,23 +390,6 @@ gsk_gl_glyph_cache_begin_frame (GskGLGlyphCache *self,
             }
         }
     }
-  g_hash_table_unref (removed);
 
   GSK_NOTE(GLYPH_CACHE, if (dropped > 0) g_message ("Dropped %d glyphs", dropped));
-
-#if 0
-  for (i = 0; i < self->atlases->len; i++)
-    {
-      GskGLGlyphAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image)
-        {
-          char *filename;
-
-          filename = g_strdup_printf ("glyphatlas%d-%ld.png", i, self->timestamp);
-          gsk_gl_image_write_to_png (atlas->image, driver, filename);
-          g_free (filename);
-        }
-    }
-#endif
 }
index c3ab245cf4f545ae9d1c191df05cc244f6127fc8..72fce675390ce61b677253a4e65c1be51aeda7b5 100644 (file)
@@ -9,8 +9,11 @@
 
 typedef struct
 {
+  int ref_count;
+
+  GdkDisplay *display;
   GHashTable *hash_table;
-  GPtrArray *atlases;
+  GskGLTextureAtlases *atlases;
 
   guint64 timestamp;
 } GskGLGlyphCache;
@@ -22,18 +25,12 @@ typedef struct
   guint scale; /* times 1024 */
 } GlyphCacheKey;
 
-typedef struct _DirtyGlyph DirtyGlyph;
 typedef struct _GskGLCachedGlyph GskGLCachedGlyph;
 
-struct _DirtyGlyph
-{
-  GlyphCacheKey *key;
-  GskGLCachedGlyph *value;
-};
-
 struct _GskGLCachedGlyph
 {
   GskGLTextureAtlas *atlas;
+  guint texture_id;
 
   float tx;
   float ty;
@@ -45,25 +42,25 @@ struct _GskGLCachedGlyph
   int draw_width;
   int draw_height;
 
-  float scale;
-
   guint64 timestamp;
   guint used: 1;
 };
 
 
-void                     gsk_gl_glyph_cache_init            (GskGLGlyphCache        *self);
-void                     gsk_gl_glyph_cache_free            (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver);
-void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver);
-guint                    gsk_gl_glyph_cache_get_glyph_texture_id (GskGLGlyphCache        *self,
-                                                             GskGLDriver            *driver,
-                                                             const GskGLCachedGlyph *glyph);
-const GskGLCachedGlyph * gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
-                                                             gboolean                create,
+GskGLGlyphCache *        gsk_gl_glyph_cache_new             (GdkDisplay *display,
+                                                             GskGLTextureAtlases *atlases);
+GskGLGlyphCache *        gsk_gl_glyph_cache_ref             (GskGLGlyphCache *self);
+void                     gsk_gl_glyph_cache_unref           (GskGLGlyphCache        *self);
+void                     gsk_gl_glyph_cache_begin_frame     (GskGLGlyphCache        *self);
+gboolean                 gsk_gl_glyph_cache_lookup          (GskGLGlyphCache        *self,
                                                              PangoFont              *font,
                                                              PangoGlyph              glyph,
-                                                             float                   scale);
+                                                             float                   scale,
+                                                             GskGLCachedGlyph *cached_glyph_out);
+void                    gsk_gl_glyph_cache_get_texture      (GskGLDriver *driver,
+                                                             PangoFont   *font,
+                                                             PangoGlyph   glyph,
+                                                             float        scale,
+                                                             GskGLCachedGlyph *glyph_out);
 
 #endif
index cc823f496b85c9db431d6690c9ac556656fda523..c0ad9af3743fc53abb915cf67181c42c5f83372d 100644 (file)
@@ -1,12 +1,11 @@
 #include "gskgliconcacheprivate.h"
 #include "gskgltextureatlasprivate.h"
 #include "gdk/gdktextureprivate.h"
+#include "gdk/gdkglcontextprivate.h"
 
 #include <epoxy/gl.h>
 
-#define ATLAS_SIZE    (1024)
 #define MAX_FRAME_AGE (5 * 60)
-#define MAX_UNUSED_RATIO 0.8
 
 typedef struct
 {
@@ -22,57 +21,49 @@ icon_data_free (gpointer p)
   g_free (p);
 }
 
-static void
-free_atlas (gpointer v)
+GskGLIconCache *
+gsk_gl_icon_cache_new (GdkDisplay *display,
+                       GskGLTextureAtlases *atlases)
 {
-  GskGLTextureAtlas *atlas = v;
+  GskGLIconCache *self;
+
+  self = g_new0 (GskGLIconCache, 1);
 
-  g_assert (atlas->image.texture_id == 0);
-  gsk_gl_texture_atlas_free (atlas);
+  self->display = display;
+  self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+  self->atlases = gsk_gl_texture_atlases_ref (atlases);
+  self->ref_count = 1;
 
-  g_free (atlas);
+  return self;
 }
 
-void
-gsk_gl_icon_cache_init (GskGLIconCache *self,
-                        GskRenderer    *renderer,
-                        GskGLDriver    *gl_driver)
+GskGLIconCache *
+gsk_gl_icon_cache_ref (GskGLIconCache *self)
 {
-  self->renderer = renderer;
-  self->gl_driver = gl_driver;
+  self->ref_count++;
 
-  self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)free_atlas);
-  self->icons = g_hash_table_new_full (NULL, NULL, NULL, icon_data_free);
+  return self;
 }
 
 void
-gsk_gl_icon_cache_free (GskGLIconCache *self)
+gsk_gl_icon_cache_unref (GskGLIconCache *self)
 {
-  guint i, p;
+  g_assert (self->ref_count > 0);
 
-  for (i = 0, p = self->atlases->len; i < p; i ++)
+  if (self->ref_count == 1)
     {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (atlas->image.texture_id != 0)
-        {
-          gsk_gl_image_destroy (&atlas->image, self->gl_driver);
-          atlas->image.texture_id = 0;
-        }
-
-      gsk_gl_texture_atlas_free (atlas);
-
-      g_free (atlas);
+      gsk_gl_texture_atlases_unref (self->atlases);
+      g_hash_table_unref (self->icons);
+      g_free (self);
+      return;
     }
-  g_ptr_array_free (self->atlases, TRUE);
 
-  g_hash_table_unref (self->icons);
+  self->ref_count--;
 }
 
 void
 gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
 {
-  gint i, p;
   GHashTableIter iter;
   GdkTexture *texture;
   IconData *icon_data;
@@ -81,45 +72,30 @@ gsk_gl_icon_cache_begin_frame (GskGLIconCache *self)
   g_hash_table_iter_init (&iter, self->icons);
   while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
     {
-      icon_data->frame_age ++;
+      guint pos;
 
-      if (icon_data->frame_age > MAX_FRAME_AGE)
+      if (!g_ptr_array_find (self->atlases->atlases, icon_data->atlas, &pos))
         {
-
-          if (icon_data->used)
-            {
-              const int w = icon_data->texture_rect.size.width  * ATLAS_SIZE;
-              const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
-
-              gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
-              icon_data->used = FALSE;
-            }
-          /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
-           * This way we can revive it when we use it again. */
+          g_hash_table_iter_remove (&iter);
         }
-    }
-
-  for (i = 0, p = self->atlases->len; i < p; i ++)
-    {
-      GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i);
-
-      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_UNUSED_RATIO)
+      else
         {
-          g_hash_table_iter_init (&iter, self->icons);
-          while (g_hash_table_iter_next (&iter, (gpointer *)&texture, (gpointer *)&icon_data))
-            {
-              if (icon_data->atlas == atlas)
-                g_hash_table_iter_remove (&iter);
-            }
+          icon_data->frame_age ++;
 
-          if (atlas->image.texture_id != 0)
+          if (icon_data->frame_age > MAX_FRAME_AGE)
             {
-              gsk_gl_image_destroy (&atlas->image, self->gl_driver);
-              atlas->image.texture_id = 0;
-            }
 
-          g_ptr_array_remove_index_fast (self->atlases, i);
-          i --; /* Check the current index again */
+              if (icon_data->used)
+                {
+                  const int w = icon_data->texture_rect.size.width  * icon_data->atlas->width;
+                  const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
+
+                  gsk_gl_texture_atlas_mark_unused (icon_data->atlas, w + 2, h + 2);
+                  icon_data->used = FALSE;
+                }
+              /* We do NOT remove the icon here. Instead, We wait until we drop the entire atlas.
+               * This way we can revive it when we use it again. */
+            }
         }
     }
 }
@@ -154,6 +130,16 @@ pad_surface (cairo_surface_t *surface)
   return padded;
 }
 
+static void
+upload_region_or_else (GskGLIconCache *self,
+                       guint           texture_id,
+                       GskImageRegion *region)
+{
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+  glTextureSubImage2D (texture_id, 0, region->x, region->y, region->width, region->height,
+                   GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, region->data);
+}
+
 void
 gsk_gl_icon_cache_lookup_or_add (GskGLIconCache  *self,
                                  GdkTexture      *texture,
@@ -167,84 +153,60 @@ gsk_gl_icon_cache_lookup_or_add (GskGLIconCache  *self,
       icon_data->frame_age = 0;
       if (!icon_data->used)
         {
-          const int w = icon_data->texture_rect.size.width  * ATLAS_SIZE;
-          const int h = icon_data->texture_rect.size.height * ATLAS_SIZE;
+          const int w = icon_data->texture_rect.size.width  * icon_data->atlas->width;
+          const int h = icon_data->texture_rect.size.height * icon_data->atlas->height;
 
           gsk_gl_texture_atlas_mark_used (icon_data->atlas, w + 2, h + 2);
           icon_data->used = TRUE;
         }
 
-      *out_texture_id = icon_data->atlas->image.texture_id;
+      *out_texture_id = icon_data->atlas->texture_id;
       *out_texture_rect = icon_data->texture_rect;
       return;
     }
 
   /* texture not on any atlas yet. Find a suitable one. */
   {
-    const int twidth = gdk_texture_get_width (texture);
-    const int theight = gdk_texture_get_height (texture);
-    int packed_x, packed_y;
+    const int width = gdk_texture_get_width (texture);
+    const int height = gdk_texture_get_height (texture);
     GskGLTextureAtlas *atlas = NULL;
-    guint i, p;
+    int packed_x = 0;
+    int packed_y = 0;
     GskImageRegion region;
     cairo_surface_t *surface;
     cairo_surface_t *padded_surface;
 
-    g_assert (twidth  < ATLAS_SIZE);
-    g_assert (theight < ATLAS_SIZE);
-
-    for (i = 0, p = self->atlases->len; i < p; i ++)
-      {
-        atlas = g_ptr_array_index (self->atlases, i);
-
-        if (gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y))
-          {
-            packed_x += 1;
-            packed_y += 1;
-            break;
-          }
-
-        atlas = NULL;
-      }
-
-    if (!atlas)
-      {
-        /* No atlas has enough space, so create a new one... */
-        atlas = g_malloc (sizeof (GskGLTextureAtlas));
-        gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
-        gsk_gl_image_create (&atlas->image, self->gl_driver, atlas->width, atlas->height, GL_LINEAR, GL_LINEAR);
-        /* Pack it onto that one, which surely has enought space... */
-        gsk_gl_texture_atlas_pack (atlas, twidth + 2, theight + 2, &packed_x, &packed_y);
-        packed_x += 1;
-        packed_y += 1;
-
-        g_ptr_array_add (self->atlases, atlas);
-      }
+    gsk_gl_texture_atlases_pack (self->atlases, width + 2, height + 2, &atlas, &packed_x, &packed_y);
 
     icon_data = g_new0 (IconData, 1);
     icon_data->atlas = atlas;
     icon_data->frame_age = 0;
     icon_data->used = TRUE;
     graphene_rect_init (&icon_data->texture_rect,
-                        (float)packed_x / ATLAS_SIZE,
-                        (float)packed_y / ATLAS_SIZE,
-                        (float)twidth   / ATLAS_SIZE,
-                        (float)theight  / ATLAS_SIZE);
+                        (float)(packed_x + 1) / atlas->width,
+                        (float)(packed_y + 1) / atlas->height,
+                        (float)width / atlas->width,
+                        (float)height / atlas->height);
 
     g_hash_table_insert (self->icons, texture, icon_data);
 
     /* actually upload the texture */
     surface = gdk_texture_download_surface (texture);
     padded_surface = pad_surface (surface);
-    region.x = packed_x - 1;
-    region.y = packed_y - 1;
-    region.width = twidth + 2;
-    region.height = theight + 2;
+    region.x = packed_x;
+    region.y = packed_y;
+    region.width = width + 2;
+    region.height = height + 2;
     region.data = cairo_image_surface_get_data (padded_surface);
 
-    gsk_gl_image_upload_region (&atlas->image, self->gl_driver, &region);
+    gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+                                            "Uploading texture");
+
+    upload_region_or_else (self, atlas->texture_id, &region);
+
+    gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
 
-    *out_texture_id = atlas->image.texture_id;
+    *out_texture_id = atlas->texture_id;
     *out_texture_rect = icon_data->texture_rect;
 
     cairo_surface_destroy (surface);
index 883a274ded7c3c9c2249ffb390187bec320592a1..ade371b9301d75df5a5717b2b945b7de61ebc27b 100644 (file)
 
 typedef struct
 {
+  int ref_count;
+
+  GdkDisplay *display;
   GskGLDriver *gl_driver;
-  GskRenderer *renderer;
 
-  GPtrArray *atlases;
+  GskGLTextureAtlases *atlases;
   GHashTable *icons; /* GdkTexture -> IconData */
 
 } GskGLIconCache;
 
-void             gsk_gl_icon_cache_init           (GskGLIconCache        *self,
-                                                   GskRenderer            *renderer,
-                                                   GskGLDriver            *gl_driver);
-void             gsk_gl_icon_cache_free           (GskGLIconCache        *self);
+GskGLIconCache * gsk_gl_icon_cache_new            (GdkDisplay *display,
+                                                   GskGLTextureAtlases *atlases);
+GskGLIconCache * gsk_gl_icon_cache_ref            (GskGLIconCache        *self);
+void             gsk_gl_icon_cache_unref          (GskGLIconCache        *self);
 void             gsk_gl_icon_cache_begin_frame    (GskGLIconCache        *self);
 void             gsk_gl_icon_cache_lookup_or_add  (GskGLIconCache        *self,
                                                    GdkTexture            *texture,
index c2f5e0abdee55c900b1749498059f674c2609b3c..a288fea50bcf7cefe1e27057e985e2ddedc03f6f 100644 (file)
@@ -335,8 +335,9 @@ struct _GskGLRenderer
   RenderOpBuilder op_builder;
   GArray *render_ops;
 
-  GskGLGlyphCache glyph_cache;
-  GskGLIconCache icon_cache;
+  GskGLTextureAtlases *atlases;
+  GskGLGlyphCache *glyph_cache;
+  GskGLIconCache *icon_cache;
   GskGLShadowCache shadow_cache;
 
 #ifdef G_ENABLE_DEBUG
@@ -571,7 +572,7 @@ render_text_node (GskGLRenderer   *self,
   for (i = 0; i < num_glyphs; i++)
     {
       const PangoGlyphInfo *gi = &glyphs[i];
-      const GskGLCachedGlyph *glyph;
+      GskGLCachedGlyph glyph;
       float glyph_x, glyph_y, glyph_w, glyph_h;
       float tx, ty, tx2, ty2;
       double cx;
@@ -580,32 +581,41 @@ render_text_node (GskGLRenderer   *self,
       if (gi->glyph == PANGO_GLYPH_EMPTY)
         continue;
 
-      glyph = gsk_gl_glyph_cache_lookup (&self->glyph_cache,
-                                         TRUE,
-                                         (PangoFont *)font,
-                                         gi->glyph,
-                                         text_scale);
+      gsk_gl_glyph_cache_lookup (self->glyph_cache,
+                                 (PangoFont *)font,
+                                 gi->glyph,
+                                 text_scale,
+                                 &glyph);
 
       /* e.g. whitespace */
-      if (glyph->draw_width <= 0 || glyph->draw_height <= 0 || glyph->scale <= 0)
+      if (glyph.draw_width <= 0 || glyph.draw_height <= 0)
         goto next;
 
+      /* big glyphs are not cached */
+      if (!glyph.texture_id)
+        {
+          gsk_gl_glyph_cache_get_texture (self->gl_driver,
+                                          (PangoFont *)font,
+                                          gi->glyph,
+                                          text_scale,
+                                          &glyph);
+          g_assert (glyph.texture_id != 0);
+        }
+
       cx = (double)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
       cy = (double)(gi->geometry.y_offset) / PANGO_SCALE;
 
-      ops_set_texture (builder, gsk_gl_glyph_cache_get_glyph_texture_id (&self->glyph_cache,
-                                                                         self->gl_driver,
-                                                                         glyph));
+      ops_set_texture (builder, glyph.texture_id);
 
-      tx  = glyph->tx;
-      ty  = glyph->ty;
-      tx2 = tx + glyph->tw;
-      ty2 = ty + glyph->th;
+      tx  = glyph.tx;
+      ty  = glyph.ty;
+      tx2 = tx + glyph.tw;
+      ty2 = ty + glyph.th;
 
-      glyph_x = x + cx + glyph->draw_x;
-      glyph_y = y + cy + glyph->draw_y;
-      glyph_w = glyph->draw_width;
-      glyph_h = glyph->draw_height;
+      glyph_x = x + cx + glyph.draw_x;
+      glyph_y = y + cy + glyph.draw_y;
+      glyph_w = glyph.draw_width;
+      glyph_h = glyph.draw_height;
 
       ops_draw (builder, (GskQuadVertex[GL_N_VERTICES]) {
         { { glyph_x,           glyph_y           }, { tx,  ty  }, },
@@ -829,12 +839,12 @@ render_texture_node (GskGLRenderer       *self,
       int texture_id;
       float tx = 0, ty = 0, tx2 = 1, ty2 = 1;
 
-      if (texture->width <= 64 &&
-          texture->height <= 64)
+      if (texture->width <= 128 &&
+          texture->height <= 128)
         {
           graphene_rect_t trect;
 
-          gsk_gl_icon_cache_lookup_or_add (&self->icon_cache,
+          gsk_gl_icon_cache_lookup_or_add (self->icon_cache,
                                            texture,
                                            &texture_id,
                                            &trect);
@@ -2466,6 +2476,68 @@ gsk_gl_renderer_create_programs (GskGLRenderer  *self,
   return TRUE;
 }
 
+static GskGLTextureAtlases *
+get_texture_atlases_for_display (GdkDisplay *display)
+{
+  GskGLTextureAtlases *atlases;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_texture_atlases_new ();
+
+  atlases = (GskGLTextureAtlases*)g_object_get_data (G_OBJECT (display), "gsk-gl-texture-atlases");
+  if (atlases == NULL)
+    {
+      atlases = gsk_gl_texture_atlases_new ();
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-texture-atlases",
+                              gsk_gl_texture_atlases_ref (atlases),
+                              (GDestroyNotify) gsk_gl_texture_atlases_unref);
+    }
+
+  return atlases;
+}
+
+static GskGLGlyphCache *
+get_glyph_cache_for_display (GdkDisplay *display,
+                             GskGLTextureAtlases *atlases)
+{
+  GskGLGlyphCache *glyph_cache;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_glyph_cache_new (display, atlases);
+
+  glyph_cache = (GskGLGlyphCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-glyph-cache");
+  if (glyph_cache == NULL)
+    {
+      glyph_cache = gsk_gl_glyph_cache_new (display, atlases);
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-glyph-cache",
+                              gsk_gl_glyph_cache_ref (glyph_cache),
+                              (GDestroyNotify) gsk_gl_glyph_cache_unref);
+    }
+
+  return glyph_cache;
+}
+
+static GskGLIconCache *
+get_icon_cache_for_display (GdkDisplay *display,
+                            GskGLTextureAtlases *atlases)
+{
+  GskGLIconCache *icon_cache;
+
+  if (g_getenv ("GSK_NO_SHARED_CACHES"))
+    return gsk_gl_icon_cache_new (display, atlases);
+
+  icon_cache = (GskGLIconCache*)g_object_get_data (G_OBJECT (display), "gsk-gl-icon-cache");
+  if (icon_cache == NULL)
+    {
+      icon_cache = gsk_gl_icon_cache_new (display, atlases);
+      g_object_set_data_full (G_OBJECT (display), "gsk-gl-icon-cache",
+                              gsk_gl_icon_cache_ref (icon_cache),
+                              (GDestroyNotify) gsk_gl_icon_cache_unref);
+    }
+
+  return icon_cache;
+}
+
 static gboolean
 gsk_gl_renderer_realize (GskRenderer  *renderer,
                          GdkSurface    *surface,
@@ -2496,8 +2568,9 @@ gsk_gl_renderer_realize (GskRenderer  *renderer,
   if (!gsk_gl_renderer_create_programs (self, error))
     return FALSE;
 
-  gsk_gl_glyph_cache_init (&self->glyph_cache);
-  gsk_gl_icon_cache_init (&self->icon_cache, renderer, self->gl_driver);
+  self->atlases = get_texture_atlases_for_display (gdk_surface_get_display (surface));
+  self->glyph_cache = get_glyph_cache_for_display (gdk_surface_get_display (surface), self->atlases);
+  self->icon_cache = get_icon_cache_for_display (gdk_surface_get_display (surface), self->atlases);
   gsk_gl_shadow_cache_init (&self->shadow_cache);
 
   return TRUE;
@@ -2522,8 +2595,9 @@ gsk_gl_renderer_unrealize (GskRenderer *renderer)
   for (i = 0; i < GL_N_PROGRAMS; i ++)
     glDeleteProgram (self->programs[i].id);
 
-  gsk_gl_glyph_cache_free (&self->glyph_cache, self->gl_driver);
-  gsk_gl_icon_cache_free (&self->icon_cache);
+  g_clear_pointer (&self->glyph_cache, gsk_gl_glyph_cache_unref);
+  g_clear_pointer (&self->icon_cache, gsk_gl_icon_cache_unref);
+  g_clear_pointer (&self->atlases, gsk_gl_texture_atlases_unref);
   gsk_gl_shadow_cache_free (&self->shadow_cache, self->gl_driver);
 
   g_clear_object (&self->gl_profiler);
@@ -3089,8 +3163,9 @@ gsk_gl_renderer_do_render (GskRenderer           *renderer,
                               ORTHO_FAR_PLANE);
   graphene_matrix_scale (&projection, 1, -1, 1);
 
-  gsk_gl_glyph_cache_begin_frame (&self->glyph_cache, self->gl_driver);
-  gsk_gl_icon_cache_begin_frame (&self->icon_cache);
+  gsk_gl_texture_atlases_begin_frame (self->atlases);
+  gsk_gl_glyph_cache_begin_frame (self->glyph_cache);
+  gsk_gl_icon_cache_begin_frame (self->icon_cache);
   gsk_gl_shadow_cache_begin_frame (&self->shadow_cache, self->gl_driver);
 
   ops_set_projection (&self->op_builder, &projection);
index 3419a2d5a251f6960d38650ff7adc4a29c56c086..06f6fcbedce9a219fe2cd32f5f3d30f7bb8e43c8 100644 (file)
@@ -1,6 +1,169 @@
 
+#include "config.h"
 #include "gskgltextureatlasprivate.h"
+#include "gskdebugprivate.h"
+#include "gdkglcontextprivate.h"
+#include <epoxy/gl.h>
 
+#define ATLAS_SIZE (512)
+#define MAX_OLD_RATIO 0.5
+
+static void
+free_atlas (gpointer v)
+{
+  GskGLTextureAtlas *atlas = v;
+
+  gsk_gl_texture_atlas_free (atlas);
+
+  g_free (atlas);
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_new (void)
+{
+  GskGLTextureAtlases *atlases;
+
+  atlases = g_new (GskGLTextureAtlases, 1);
+  atlases->atlases = g_ptr_array_new_with_free_func (free_atlas);
+
+  atlases->ref_count = 1;
+
+  return atlases;
+}
+
+GskGLTextureAtlases *
+gsk_gl_texture_atlases_ref (GskGLTextureAtlases *atlases)
+{
+  atlases->ref_count++;
+
+  return atlases;
+}
+
+void
+gsk_gl_texture_atlases_unref (GskGLTextureAtlases *atlases)
+{
+  g_assert (atlases->ref_count > 0);
+
+  if (atlases->ref_count == 1)
+    {
+      g_ptr_array_unref (atlases->atlases);
+      g_free (atlases);
+      return;
+    }
+
+  atlases->ref_count--;
+}
+
+#if 1
+static void
+write_atlas_to_png (GskGLTextureAtlas *atlas,
+                    const char        *filename)
+{
+  int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
+  guchar *data = g_malloc (atlas->height * stride);
+  cairo_surface_t *s;
+
+  glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
+  glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+  s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
+  cairo_surface_write_to_png (s, filename);
+
+  cairo_surface_destroy (s);
+  g_free (data);
+}
+#endif
+
+void
+gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases)
+{
+  int i;
+
+  for (i = atlases->atlases->len - 1; i >= 0; i--)
+    {
+      GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+      if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO)
+        {
+          GSK_NOTE(GLYPH_CACHE,
+                   g_message ("Dropping atlas %d (%g.2%% old)", i,
+                              gsk_gl_texture_atlas_get_unused_ratio (atlas)));
+
+          if (atlas->texture_id != 0)
+            {
+              glDeleteTextures (1, &atlas->texture_id);
+              atlas->texture_id = 0;
+            }
+
+          g_ptr_array_remove_index (atlases->atlases, i);
+       }
+    }
+
+#if 1
+  {
+    static guint timestamp;
+
+    timestamp++;
+    if (timestamp % 10 == 0)
+      for (i = 0; i < atlases->atlases->len; i++)
+        {
+          GskGLTextureAtlas *atlas = g_ptr_array_index (atlases->atlases, i);
+
+          if (atlas->texture_id)
+            {
+              char *filename;
+
+              filename = g_strdup_printf ("textureatlas%d-%u.png", i, timestamp);
+              write_atlas_to_png (atlas, filename);
+              g_free (filename);
+            }
+         }
+   }
+#endif
+}
+
+gboolean
+gsk_gl_texture_atlases_pack (GskGLTextureAtlases *atlases,
+                             int                  width,
+                             int                  height,
+                             GskGLTextureAtlas  **atlas_out,
+                             int                 *out_x,
+                             int                 *out_y)
+{
+  GskGLTextureAtlas *atlas;
+  int x, y;
+  int i;
+
+  g_assert (width  < ATLAS_SIZE);
+  g_assert (height < ATLAS_SIZE);
+
+  for (i = 0; i < atlases->atlases->len; i++)
+    {
+      atlas = g_ptr_array_index (atlases->atlases, i);
+
+      if (gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y))
+        break;
+
+      atlas = NULL;
+    }
+
+  if (atlas == NULL)
+    {
+      /* No atlas has enough space, so create a new one... */
+      atlas = g_malloc (sizeof (GskGLTextureAtlas));
+      gsk_gl_texture_atlas_init (atlas, ATLAS_SIZE, ATLAS_SIZE);
+      gsk_gl_texture_atlas_realize (atlas);
+      g_ptr_array_add (atlases->atlases, atlas);
+
+      /* Pack it onto that one, which surely has enough space... */
+      gsk_gl_texture_atlas_pack (atlas, width, height, &x, &y);
+    }
+
+  *atlas_out = atlas;
+  *out_x = x;
+  *out_y = y;
+
+  return TRUE;
+}
 
 void
 gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
@@ -9,7 +172,7 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
 {
   memset (self, 0, sizeof (*self));
 
-  self->image.texture_id = 0;
+  self->texture_id = 0;
   self->width = width;
   self->height = height;
 
@@ -20,11 +183,19 @@ gsk_gl_texture_atlas_init (GskGLTextureAtlas *self,
                      width, height,
                      self->nodes,
                      width);
+
+  gsk_gl_texture_atlas_realize (self);
 }
 
 void
 gsk_gl_texture_atlas_free (GskGLTextureAtlas *self)
 {
+  if (self->texture_id != 0)
+    {
+      glDeleteTextures (1, &self->texture_id);
+      self->texture_id = 0;
+    }
+
   g_clear_pointer (&self->nodes, g_free);
 }
 
@@ -82,3 +253,43 @@ gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self)
   return 0.0;
 }
 
+/* Not using gdk_gl_driver_create_texture here, since we want
+ * this texture to survive the driver and stay around until
+ * the display gets closed.
+ */
+static guint
+create_shared_texture (int width,
+                       int height)
+{
+  guint texture_id;
+
+  glGenTextures (1, &texture_id);
+  glBindTexture (GL_TEXTURE_2D, texture_id);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+  if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+  else
+    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+  glBindTexture (GL_TEXTURE_2D, 0);
+
+  return texture_id;
+}
+
+void
+gsk_gl_texture_atlas_realize (GskGLTextureAtlas *atlas)
+{
+  if (atlas->texture_id)
+    return;
+
+  atlas->texture_id = create_shared_texture (atlas->width, atlas->height);
+  gdk_gl_context_label_object_printf (gdk_gl_context_get_current (),
+                                      GL_TEXTURE, atlas->texture_id,
+                                      "Glyph atlas %d", atlas->texture_id);
+}
index d3aea5414213bc6ec29a33d37798f444a845c81c..2864fd8d050bcdf4b154a3d535290986aaecbf56 100644 (file)
@@ -14,7 +14,7 @@ struct _GskGLTextureAtlas
   int width;
   int height;
 
-  GskGLImage image;
+  guint texture_id;
 
   int unused_pixels; /* Pixels of rects that have been used at some point,
                         But are now unused. */
@@ -23,12 +23,34 @@ struct _GskGLTextureAtlas
 };
 typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
 
+struct _GskGLTextureAtlases
+{
+  int ref_count;
+
+  GPtrArray *atlases;
+};
+typedef struct _GskGLTextureAtlases GskGLTextureAtlases;
+
+GskGLTextureAtlases *gsk_gl_texture_atlases_new         (void);
+GskGLTextureAtlases *gsk_gl_texture_atlases_ref         (GskGLTextureAtlases *atlases);
+void                 gsk_gl_texture_atlases_unref       (GskGLTextureAtlases *atlases);
+
+void                 gsk_gl_texture_atlases_begin_frame (GskGLTextureAtlases *atlases);
+gboolean             gsk_gl_texture_atlases_pack        (GskGLTextureAtlases *atlases,
+                                                         int                  width,
+                                                         int                  height,
+                                                         GskGLTextureAtlas  **atlas_out,
+                                                         int                 *out_x,
+                                                         int                 *out_y);
+
 void        gsk_gl_texture_atlas_init              (GskGLTextureAtlas       *self,
                                                     int                      width,
                                                     int                      height);
 
 void        gsk_gl_texture_atlas_free              (GskGLTextureAtlas       *self);
 
+void        gsk_gl_texture_atlas_realize           (GskGLTextureAtlas       *self);
+
 void        gsk_gl_texture_atlas_mark_unused       (GskGLTextureAtlas       *self,
                                                     int                      width,
                                                     int                      height);